package com.edgar.plugin.hooks

import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.tasks.ProcessAndroidResources
import com.android.sdklib.BuildToolInfo
import com.edgar.plugin.ResourceCollect
import com.edgar.plugin.aapt.Aapt
import com.edgar.plugin.entry.ResourceEntry
import com.edgar.plugin.entry.StyleableEntry
import com.edgar.plugin.utils.ZipUtil
import com.google.common.collect.ListMultimap
import com.google.common.io.Files
import org.gradle.api.Project
import org.gradle.api.file.FileTree

public class ProcessResourceTaskHooker extends BaseHooker<ProcessAndroidResources>{

    private int packageId = 0x6f
    private ResourceCollect resourceCollect

    public ProcessResourceTaskHooker(Project project,ApplicationVariant variant) {
        super(project,variant)
    }

    @Override
    def getTaskName() {
        return "process${variant.name.capitalize()}Resources"
    }

    private static def getResourceApFile(ProcessAndroidResources task){
        final String variantName = task.getVariantName().toLowerCase()
        File resDir = task.resPackageOutputFolder
        println "resDir:${resDir}"
        return new File(resDir,"resources-${variantName}.ap_")
    }

    @Override
    void beforeExecute(ProcessAndroidResources task) {
    }

    @Override
    void afterExecute(ProcessAndroidResources task) {
        // Unpack resources.ap_
//        final String variantName = aaptTask.getVariantName().toLowerCase()
//        File resDir = aaptTask.resPackageOutputFolder
//        println "resDir:${resDir}"
        File apFile = getResourceApFile(task)
        println "resources apFile:${apFile.absolutePath}"
        FileTree apFiles = project.zipTree(apFile)
        File resourcesDir = new File(task.resPackageOutputFolder.parentFile, Files.getNameWithoutExtension(apFile.name))
        println "resources dir:${resourcesDir.absolutePath}"
        resourcesDir.deleteDir()
        project.copy {
            from apFiles
            into resourcesDir
            include 'AndroidManifest.xml'
            include 'resources.arsc'
            include 'res/**/*'
        }
        // Modify assets
        File symbolFile = task.getTextSymbolOutputFile()
        println "symbolFile:${symbolFile.absolutePath}"
//        prepareSplit(symbolFile)
        File sourceOutputDir = task.sourceOutputDir
        File rJavaFile = new File(sourceOutputDir, "${packageName.replace(".","/")}/R.java")
        def rev = project.android.buildToolsRevision
        int noResourcesFlag = 0
        resourceCollect = new ResourceCollect(symbolFile,packageId)
        resourceCollect.collect()
        def retainedTypes = convertResourcesForAapt(resourceCollect.pluginResources)
        def retainedStyleables = convertStyleablesForAapt(resourceCollect.pluginStyleables)

        def filteredResources = [] as HashSet<String>
        def updatedResources = [] as HashSet<String>
        def libRefTable = ["${packageId}":packageName]
        def idMaps = resourceCollect.resIdMap
        Aapt aapt = new Aapt(resourcesDir, rJavaFile, symbolFile, rev)
        aapt.filterResources(retainedTypes, filteredResources)
        println "[${project.name}] split library res files..."
        aapt.filterPackage(retainedTypes, packageId, idMaps, libRefTable,
                retainedStyleables, updatedResources)
//        if (retainedTypes != null && this.retainedTypes.size() > 0) {
//            aapt.filterResources(retainedTypes, filteredResources)
//            println "[${project.name}] split library res files..."
//            aapt.filterPackage(retainedTypes, packageId, idMaps, libRefTable,
//                    retainedStyleables, updatedResources)
////            println "[${project.name}] slice asset package and reset package id..."
////            String pkg = packageName
//            // Overwrite the aapt-generated R.java with full edition
////            rJavaFile.delete()
////            aapt.generateRJava(rJavaFile, pkg, this.allTypes, this.allStyleables)
////            println "[${project.name}] split library R.java files..."
//        } else {
//            println 'No Resource To Modify'
//        }
//        String aaptExe = aaptTask.buildTools.getPath(BuildToolInfo.PathId.AAPT)
        // Delete filtered entries.
        // Cause there is no `aapt update' command supported, so for the updated resources
        // we also delete first and run `aapt add' later.
//        filteredResources.addAll(updatedResources)
        ZipUtil.with(apFile).deleteAll(filteredResources+updatedResources)
        // Re-add updated entries.
        // $ aapt add resources.ap_ file1 file2 ...
        project.exec {
            executable task.buildTools.getPath(BuildToolInfo.PathId.AAPT)
            workingDir resourcesDir
            args 'add', apFile.path
            args updatedResources
            // store the output instead of printing to the console
            standardOutput = new ByteArrayOutputStream()
        }
        updateRJava(aapt,task.sourceOutputDir)
    }

    def updateRJava(Aapt aapt, File sourceOutputDir) {

        sourceOutputDir.deleteDir()

        def packagePath = packageName.replace(".",File.separator)
        def rSourceFile = new File(sourceOutputDir, "${packagePath}${File.separator}R.java")
        aapt.generateRJava(rSourceFile, packageName, resourceCollect.pluginResources, resourceCollect.pluginStyleables)

        def splitRSourceFile = new File(sourceOutputDir.parentFile, "plugin${File.separator}${packagePath}${File.separator}R.java")
        aapt.generateRJava(splitRSourceFile, packageName, resourceCollect.pluginResources, resourceCollect.pluginStyleables)
//        virtualApk.splitRJavaFile = splitRSourceFile

//        virtualApk.retainedAarLibs.each {
//            def aarPackage = it.package
//            def rJavaFile = new File(sourceOutputDir, "${aarPackage.replace('.'.charAt(0), File.separatorChar)}${File.separator}R.java")
//            aapt.generateRJava(rJavaFile, aarPackage, it.aarResources, it.aarStyleables)
//        }
    }

    /**
     * We use the third party library to modify the ASRC file,
     * this method used to transform resource data into the structure of the library
     * @param pluginResources Map of plugin resources
     */
    def convertResourcesForAapt(ListMultimap<String, ResourceEntry> pluginResources) {
        def retainedTypes = []
        retainedTypes.add(0,  [name: 'placeholder', id: Aapt.ID_NO_ATTR, entries: []])//attr 占位

        pluginResources.keySet().each { resType ->
            def firstEntry = pluginResources.get(resType).get(0)
            def typeEntry = [ type: "int", name: resType,
                              id: parseTypeIdFromResId(firstEntry.resourceId),
                              _id: parseTypeIdFromResId(firstEntry.newResourceId),
                              entries: []]

            pluginResources.get(resType).each { resEntry ->
                typeEntry.entries.add([
                        name : resEntry.resourceName,
                        id : parseEntryIdFromResId(resEntry.resourceId),
                        _id: parseEntryIdFromResId(resEntry.newResourceId),
                        v : resEntry.resourceId, _v : resEntry.newResourceId,
                        vs: resEntry.hexResourceId, _vs : resEntry.hexNewResourceId])
            }

            if (resType == 'attr') {
                retainedTypes.set(0, typeEntry)
            } else {
                retainedTypes.add(typeEntry)
            }
        }

        return retainedTypes
    }

    /**
     * Transform styleable data into the structure of the aapt library
     * @param pluginStyleables Map of plugin styleables
     */
    def convertStyleablesForAapt(List<StyleableEntry> pluginStyleables) {
        def retainedStyleables = []
        pluginStyleables.each { styleableEntry ->
            retainedStyleables.add([vtype : styleableEntry.valueType,
                                    type : 'styleable',
                                    key : styleableEntry.name,
                                    idStr : styleableEntry.value])
        }
        return retainedStyleables
    }

    /**
     * Parse the type part of a android resource id
     */
    def parseTypeIdFromResId(int resourceId) {
        resourceId >> 16 & 0xFF
    }

    /**
     * Parse the entry part of a android resource id
     */
    def parseEntryIdFromResId(int resourceId) {
        resourceId & 0xFFFF
    }
}